#include <cstdio>
#include <cstring>
#include <cstdlib>
#include <vector>
#include <string>
#include "error_codes.h"

const int MAX_LEN = 100000;

struct function 
{
	std::vector <std::string> variables;
	std::vector <std::string> parametrs;
	std::vector <std::pair <int, std::string> > pointers;
	std::string name;
	std::string clean_name;
	int num_of_param;
};

char buff[MAX_LEN];

std::vector<function> parce_f (FILE *Settings)
{
	std::vector <function> all_function;
	function current_function;

	while (fgets(buff, MAX_LEN, Settings))
	{
		int ptr = 0;

		while (buff[ptr] == ' ')
			ptr++;

		if (strlen(buff) >= 1)
			buff[strlen(buff) - 1] = 0;

		switch (ptr)
		{
			case 0:
				if (current_function.name.size() > 0)
					all_function.push_back(current_function);
					current_function.name = std::string(buff, buff + strlen(buff));
					current_function.variables.clear();
					current_function.clean_name = std::string("");
					current_function.pointers.clear();
					current_function.num_of_param = 0;
					current_function.pointers.clear();
				break;
			case 2:
				current_function.variables.push_back(std::string(buff + 2, buff + strlen(buff)));	
				break;
			case 4:
				char buff2[MAX_LEN];
				int var_num;
				sscanf(buff, "%d %s", &var_num, buff2);
				current_function.pointers.push_back (std::make_pair (var_num - 1, std::string(buff2, buff2 + strlen(buff2))));
				break;
			default:
				printf ("Wrong settings format\n");
				exit(WRONG_SETTINGS);
		}
	}

	if (current_function.name.size() > 0)
		all_function.push_back(current_function);

	for (int i = 0; i < (int)all_function.size(); i++)
	{

		int beg = 0, end = all_function[i].name.size();
		std::string s(all_function[i].name);

		while (s[beg] != '(')
			beg++;

		all_function[i].clean_name = std::string(s.c_str(), s.c_str() + beg);
		for (int j = 0; j < (int)all_function[i].clean_name.size(); j++)
			if (all_function[i].clean_name[j] == ':')
				all_function[i].clean_name[j] = '_';

		if (end - beg <= 2)
			continue;

		for (int j = beg + 1; j < end; j++)
		{
			if (s[j] == ',' || s[j] == ')')
			{
				all_function[i].num_of_param++;
				all_function[i].parametrs.push_back(std::string(s.c_str() + beg + 1, s.c_str() + j));
				beg = j + 1;
			}
		}
	}

	return all_function;
}

void print_function (FILE *Output, std::vector <function> all_function)
{
	for (int i = 0; i < (int)all_function.size(); i++)
	{
		fprintf (Output, "void %s_meta_calc (", all_function[i].clean_name.c_str());
		for (int j = 0; j < (int)all_function[i].parametrs.size(); j++)
			fprintf (Output, "%s v%d, ", all_function[i].parametrs[j].c_str(), j);

		fprintf (Output, "meta* f_meta)\n");

		fprintf (Output, "{\n");

		for (int j = 0; j < (int)all_function[i].variables.size(); j++)
		{
			fprintf (Output, "	%s v%d;\n", all_function[i].variables[j].c_str(), j + (int)all_function[i].parametrs.size());
		}

		fprintf (Output, "	f_meta->num_of_ptr = %d;\n", (int)all_function[i].pointers.size());
		fprintf (Output, "	f_meta->offsets = new long[f_meta->num_of_ptr];\n");
		for (int j = 0; j < (int)all_function[i].pointers.size(); j++)
		{
			if (all_function[i].pointers[j].first != -1)
  			fprintf (Output, "	f_meta->offsets[%d] = OFFS(v%d%s);\n", j, all_function[i].pointers[j].first, all_function[i].pointers[j].second.c_str());
			else
				fprintf (Output, "	f_meta->offsets[%d] = OFFS(%s);\n", j, all_function[i].pointers[j].second.c_str());				
		}
		fprintf (Output, "}\n\n");
	}
}

void print_init (FILE *Output, std::vector <function> all_function)
{
	fprintf (Output,"void init ()\n{\n");
	for (int i = 0; i < (int)all_function.size(); i++)
		fprintf (Output,"	meta %s_meta;\n", all_function[i].clean_name.c_str());

	int counter = 0;


	for (int i = 0; i < (int)all_function.size(); i++)
	{
		fprintf (Output,"\n");
		for (int j = 0; j < (int)all_function[i].parametrs.size(); j++)
			fprintf (Output,"	%s v%d;\n", all_function[i].parametrs[j].c_str(), counter++);
		fprintf (Output,"	%s_meta_calc (", all_function[i].clean_name.c_str());

		for (int j = 0; j < (int)all_function[i].parametrs.size(); j++)
			fprintf (Output,"v%d, ", counter - (int)all_function[i].parametrs.size() + j);

		fprintf (Output,"&%s_meta);\n", all_function[i].clean_name.c_str());
	}
	fprintf (Output,"\n");

	for (int i = 0; i < (int)all_function.size(); i++)
	{
		if (all_function[i].clean_name == std::string("main"))
  		fprintf (Output,"	add_function(%s_meta, std::string(%c%s%c));\n", all_function[i].clean_name.c_str(), '"', all_function[i].clean_name.c_str(), '"');	    
  	else
			fprintf (Output,"	add_function(%s_meta, std::string(%c%s%c));\n", all_function[i].clean_name.c_str(), '"', all_function[i].name.c_str(), '"');
	}

	fprintf (Output,"}\n\n");
}

void print_headers (FILE *Output, char *name)
{
	std::string s = std::string(name, name + strlen(name));

	for (int i = 0; i < (int)s.size(); i++)
		if (s[i] == '.')
		{
			s[i + 1] = 'h';
			s.erase(i + 2, s.size() - (i + 2));
		}

	fprintf (Output,"#pragma once");
	fprintf (Output,"\n");
}

int main (int argc, char *argv[])
{
	if (argc != 2)	
	{
		printf("Unknown input format.\n");
		return WRONG_INPUT;
	}

	FILE *Settings_f, *Output;

	if(!(Settings_f = fopen(argv[1], "r")))
	{
		printf ("Can't open function settings file.\n");
		return SETTINGS_NOT_AVAILABLE;
	}

	char* output_path = new char[strlen("gc.h") + strlen("output/") + 1];

	strcpy (output_path, "output/");
	strcpy (output_path + strlen(output_path), "gc.h");

	Output = fopen(output_path, "w");

	std::vector <function> all_function = parce_f(Settings_f);
	print_headers (Output, argv[1]);
	print_function (Output, all_function);
	print_init (Output, all_function);

	fclose(Settings_f), fclose(Output);

	return 0;
}



